package runesim.node.packetbuilder;

import runesim.node.model.*;
import runesim.node.net.Packet;
import runesim.node.util.StatefulEntityCollection;

public class PlayerUpdate implements PacketBuilder {
	
	Player playerToUpdate = null;
	
	public synchronized Packet getPacket(Player player) {
		playerToUpdate = player;
		
		StaticPacketBuilder mainPacket = new StaticPacketBuilder().setId(81).setSize(Packet.Size.VariableShort);
		
		StaticPacketBuilder updatePacket = new StaticPacketBuilder();
		
		int mask = getUpdateMask(player, player);
		
		appendMovementUpdate(mainPacket, player, mask);
		appendUpdateBlock(updatePacket, player, mask, null);
		
		StatefulEntityCollection<Player> players = player.getWatchedPlayers();
		mainPacket.addBits(players.size(), 8); // player count
		for(Player p : players.getKnownEntities()) { //For everyone in view
			if(!players.isRemoving(p)) {
				//Update this player's stuff to our player's update packet... how will we do chat?
				int thisPlayersMask = getUpdateMask(p, player); //player, p
				boolean thisPlayerMapAreaChanged = p.isMapAreaChanged();
				p.setMapAreaChanged(false);
				appendMovementUpdate(mainPacket, p, thisPlayersMask);
				p.setMapAreaChanged(thisPlayerMapAreaChanged);
				appendUpdateBlock(updatePacket, p, thisPlayersMask, player);
			} else {
				mainPacket.addBits(1, 1);
				mainPacket.addBits(3, 2);
			}
		}
		
		// Here's where we should add new players.
		for(Player p : players.getNewEntities()) {
			addPlayer(player, p, mainPacket, updatePacket);
		}
		
		if(updatePacket.getLength() > 0) {
			mainPacket.addBits(2047, 11);
			mainPacket.addBytes(updatePacket.toPacket().getData());
		}
		
		return mainPacket.toPacket();
	}
	
	private void appendMovementUpdate(StaticPacketBuilder spb, Player player, int updateMask) {
		if(updateMask > 0 || player.isMapAreaChanged() || player.hasMoved()) {
			spb.addBits(1, 1);
			
			/*
			 * self Update type is sent as 2 bits. Only one of these is sent at
			 * once. 0: No data to send 1: 3 bits for direction then 1 bit for
			 * update required (?) 2: 3 bits for 1st dir, 3 bits for 2nd dir,
			 * then 1 bit for update required (?) 3: 2 bits for height, 1 bit
			 * for whether to discard the walk queue, then 1 bit for update
			 * required (?), then 7 bits for y position (local probably) and 7
			 * bits for x position (local probably).
			 */

			if(player.isMapAreaChanged()) {
				spb.addBits(3, 2); // update type 3
				spb.addBits(player.getLocation().getHeight(), 2); // point
				// height
				spb.addBits(1, 1); // if true, discard the client walking queue
				spb.addBits(updateMask > 0 ? 1 : 0, 1); // is the update
				// required?
				spb.addBits(player.getLocation().getLocalY(), 7);
				spb.addBits(player.getLocation().getLocalX(), 7);
			} else if(player.hasMoved()) {
				if(player.isRunning() && player.getLastSprite() != -1) {
					spb.addBits(2, 2); // update type 2
					spb.addBits(Mob.CLIENT_SPRITES[player.getLastSprite()], 3); // direction
					// 1
					spb.addBits(Mob.CLIENT_SPRITES[player.getSprite()], 3); // direction
					// 2
					spb.addBits(updateMask > 0 ? 1 : 0, 1); // is the update
					// required?
				} else {
					spb.addBits(1, 2); // update type 1
					spb.addBits(Mob.CLIENT_SPRITES[player.getSprite()], 3); // direction
					spb.addBits(updateMask > 0 ? 1 : 0, 1); // is the update
					// required?
				}
			} else {
				spb.addBits(0, 2); // don't need to update anything and an
				// update is required
			}
		} else {
			spb.addBits(0, 1);
		}
	}
	
	private void appendUpdateBlock(StaticPacketBuilder spb, Player player, int updateMask, Player player2) {
		if(updateMask == 0)
			return;
		
		if(updateMask >= 256) {
			updateMask |= 64;
			spb.addByte((byte) (updateMask & 0xff)).addByte((byte) (updateMask >> 8));
		} else {
			spb.addByte((byte) updateMask);
		}
		
		if((updateMask & 8) != 0){ //Player animation
			spb.addLEShort(player.getEmoteIndex());
			spb.addByte((byte)-player.getEmoteDelay());
		}
		
		if((updateMask & 4) != 0){ //Player text over head
			spb.addString(player.getTextOverheadMessage());
		}
		
		if((updateMask & 128) != 0 || ((player.getLastChatMessage() != null) && (getUpdateMask(player, player2) & 128) != 0)) { // Player chat
			ChatMessage cm = player.getLastChatMessage();
			if(cm != null){
				spb.addLEShort(((cm.getColour() & 0xff) << 8) + (cm.getEffects() & 0xFF));
				spb.addByte((byte) 0);
				spb.addByte((byte) -cm.getMessage().length);
				byte[] msg = cm.getMessage();
				for(int i = msg.length - 1; i >= 0; i--) spb.addByte(msg[i]);
			}
		}
		
		if((updateMask & 16) != 0) { // Player appearance
			StaticPacketBuilder appearance = new StaticPacketBuilder();
			appearance.addByte((byte) 0) // gender
			.addByte((byte) 0); // mask
			
			WornEquipmentParent e = player.getEquipment();
			if(e.isEquipped(WornEquipmentParent.HAT)) appearance.addShort(512 + e.getID(WornEquipmentParent.HAT));
			else appearance.addByte((byte) 0);
			
			if(e.isEquipped(WornEquipmentParent.CAPE)) appearance.addShort(512 + e.getID(WornEquipmentParent.CAPE));
			else appearance.addByte((byte) 0);
			
			if(e.isEquipped(WornEquipmentParent.AMULET)) appearance.addShort(512 + e.getID(WornEquipmentParent.AMULET));
			else appearance.addByte((byte) 0);
			
			if(e.isEquipped(WornEquipmentParent.WEAPON)) appearance.addShort(512 + e.getID(WornEquipmentParent.WEAPON));
			else appearance.addByte((byte) 0);
			
			if(e.isEquipped(WornEquipmentParent.TORSO)) appearance.addShort(512 + e.getID(WornEquipmentParent.TORSO));
			else appearance.addShort(256 + player.getAppearance().getTorso());
			
			if(e.isEquipped(WornEquipmentParent.SHIELD)) appearance.addShort(512 + e.getID(WornEquipmentParent.SHIELD));
			else appearance.addByte((byte) 0);
			
			appearance.addShort(256 + player.getAppearance().getArms());
			
			if(e.isEquipped(WornEquipmentParent.LEGS)) appearance.addShort(512 + e.getID(WornEquipmentParent.LEGS));
			else appearance.addShort(256 + player.getAppearance().getLegs());
			
			appearance.addShort(256 + player.getAppearance().getHead());
			
			if(e.isEquipped(WornEquipmentParent.HANDS)) appearance.addShort(512 + e.getID(WornEquipmentParent.HANDS));
			else appearance.addShort(256 + player.getAppearance().getHands());
			
			if(e.isEquipped(WornEquipmentParent.FEET)) appearance.addShort(512 + e.getID(WornEquipmentParent.FEET));
			else appearance.addShort(256 + player.getAppearance().getFeet());
			
			appearance.addByte((byte) 0); //Player beard
			
			appearance.addByte((byte) player.getAppearance().getHairColour()).addByte((byte) player.getAppearance().getTorsoColour()).addByte((byte) player.getAppearance().getLegColour()).addByte((byte) player.getAppearance().getFeetColour()).addByte((byte) player.getAppearance().getSkinColour());
			
			for(int i : ANIMATION_INDICES) appearance.addShort(i);
			
			appearance.addLong(player.getUsernameHash());
			
			appearance.addByte((byte) player.getCombatLevel());
			appearance.addShort((byte) 0);
			
			Packet appearancePacket = appearance.toPacket();
			spb.addByte((byte) (-appearancePacket.getLength()));
			spb.addBytes(appearancePacket.getData(), 0, appearancePacket.getLength());
		}
	}
	
	private void addPlayer(Player thisPlayer, Player player, StaticPacketBuilder main, StaticPacketBuilder update) {
		main.addBits(player.getIndex(), 11);
		main.addBits(1, 1); // do we want to add the update block
		appendUpdateBlock(update, player, getUpdateMask(thisPlayer, player), thisPlayer);
		main.addBits(1, 1); // discard clients walking queue
		int dX = player.getLocation().getX() - thisPlayer.getLocation().getX(), dY = player.getLocation().getY() - thisPlayer.getLocation().getY();
		if(dX < 0)
			dX += 32;
		if(dY < 0)
			dY += 32;
		main.addBits(dY, 5).addBits(dX, 5);
	}
	
	private int getUpdateMask(Player player, Player target) {
		int updateMask = 0;
		//if(target.getLastChatMessage() != null && !player.equals(target))
			//updateMask |= 128;
		
		if(player.getTextOverheadMessage() != null)
			updateMask |= 4;
		
		if(player.getEmoteIndex() > -1){
			updateMask |= 8;
		}
		
		if(player.getKnownAppearanceId(target) < target.getAppearance().getId() || player.getAppearance().hasChanged()) {
			updateMask |= 16;
			player.setKnownAppearanceId(target, target.getAppearance().getId());
		}
		return updateMask;
	}
	
	private static final int[] ANIMATION_INDICES = { 0x328, 0x337, 0x333, 0x334, 0x335, 0x336, 0x338 };
}